<?php

CRequire('CObject');

global $_CNamedTypes;
$_CNamedTypes = array();

class CType extends CObject {
	function SetName($name, &$type) {
		global $_CNamedTypes;
		$_CNamedTypes[strtolower($name)] = &$type;
	}
	
	function &OfName($name) {
		global $_CNamedTypes;
		if (is_object(@$_CNamedTypes[strtolower($name)]))
			return $_CNamedTypes[strtolower($name)];
		else {
			$x = null; return $x;
		}
	}
	
	function AcceptsValue(&$val) {
		CAbstract('CType::AcceptsValue()', $this);
	}
	
	function Coerce(&$val) {
		CAbstract('CType::Coerce()', $this);
	}
	
	function &ArrayOf(&$type) {
		if (is_string($type)) $type = &CType::OfName($type);
		$x = &new CArrayOf($type);
		return $x;
	}
}

class CPHPBasicType extends CType {
	var $Name;
	function Init($name) {
		$this->Name = $name;
	}
	
	function AcceptsValue(&$val) {
		return strtoupper(gettype($val)) == strtoupper($this->Name);
	}
	
	function Coerce(&$val) {
		/*
		// everything can be a string
		if ($this->Name == kCStringType)
			$val = CStr($val);
		// non-object, non-array types can be converted in all other simple
		// types or into single-item arrays.
		else if (!is_object($val) && !is_array($val)) {
			// we don't use settype because it doesn't always give the right results.
			switch ($this->Name) {
				case kCBooleanType: $val = (boolean) $val; return true;
				case kCFloatType:   $val = (float) $val; return true;
				case kCNullType:    $val = null; return true;
				case kCIntegerType: $val = (integer) $val; return true;
			}
		// everything can be an array
		} else if (!is_array($val) && $this->Name == kCArrayType) {
			$val = array(&$val); return true;
		// the only "bad" conversions are array to anything else, or non-object to object,
		// or object to non-object.
		} else if ((is_array($val) && $this->Name != kCArrayType) ||
			(!is_object($val) && $this->Name != kCObjectType) ||
			(is_object($val) && $this->Name == kCObjectType))
			return CError::MalformedData(null);
		
		// there are some left out -- they're identities (an object into an object
		//	or an array into an array).
		return true;
		*/
		
		switch ($this->Name) {
			case kCStringType:
				// anything can be a string!
				$val = CStr($val); return true;
				
			case kCBooleanType:
				if (is_object($val) || is_array($val))
					return CError::MalformedData(null);
				$val = (boolean) $val;
				return true;

			case kCFloatType:
				if (is_object($val) || is_array($val))
					return CError::MalformedData(null);
				$val = (float) $val;
				return true;

			case kCIntegerType:
				if (is_object($val) || is_array($val))
					return CError::MalformedData(null);
				$val = (integer) $val;
				return true;

			case kCNullType:
				$val = null;
				return true;

			case kCArrayType:
				if (!is_array($val))
					$val = array( &$val );
				return true;

			case kCObjectType:
				return is_object($val)? true : CError::MalformedData(null);
				
			default: // defensive
				return CError::MalformedData(null);
		}
	}
}

class CArrayOf extends CType {
	var $Type;
	function Init(&$type) { $this->Type = &$type; }
	
	function Coerce(&$val) {
		if (!is_array($val))
			return CError::MalformedData(null);
		
		foreach ($val as $v) {
			if (CErr($e = $this->Type->Coerce($v)))
				return $e;
		}
		
		return true;
	}
}

class CHTMLInputText extends CType {
	function Coerce(&$val) {
		return (is_string($val) && strpos($val, "\n") === false && strpos($val, "\r") === false)? true : CError::MalformedData(null);
	}
}

class COptional extends CType {
	var $Type;
	function Init(&$type) { $this->Type = &$type; }
	function Coerce(&$val) {
		if ($val === null)
			return true;
		return $this->Type->Coerce($val);
	}
}

function _CTypeInitBasicTypes() {
	$temp = &new CPHPBasicType(kCStringType);
	CType::SetName(kCStringType, $temp);
	
	$temp = &new CPHPBasicType(kCBooleanType);
	CType::SetName(kCBooleanType, $temp);
	
	$temp = &new CPHPBasicType(kCFloatType);
	CType::SetName(kCFloatType, $temp);
	
	$temp = &new CPHPBasicType(kCArrayType);
	CType::SetName(kCArrayType, $temp);
	
	$temp = &new CPHPBasicType(kCNullType);
	CType::SetName(kCNullType, $temp);
	
	$temp = &new CPHPBasicType(kCObjectType);
	CType::SetName(kCObjectType, $temp);
	
	$temp = &new CPHPBasicType(kCIntegerType);
	CType::SetName(kCIntegerType, $temp);
	
	// HTML types
	
	$temp = &new CHTMLInputText();
	CType::SetName(kCHTMLInputText, $temp);
	
	$temp = &new COptional($temp);
	CType::SetName(kCOptionalHTMLInputText, $temp);
}

_CTypeInitBasicTypes();

?>